﻿using System;

using System.Collections.Generic;
using System.Text;
using Silvermoon.Shapes;
using Silvermoon.Utils;
using System.Drawing;
using Silvermoon.OpenGL;
using Silvermoon.Core;
using Silvermoon.Controls.Classes;
using System.IO;
using System.Reflection;
using Silvermoon.Controls.Sys;
using Silvermoon.Images;
using Silvermoon.Controls.Base;
using Silvermoon.OpenGL.Native;
using Silvermoon.Windows;
using Silvermoon.Windows.Styles;
using Silvermoon.UI;

namespace Silvermoon.Controls
{
    /// <summary>
    /// Control that provides text input for other controls.
    /// </summary>
    public class Keyboard : CompositeControlBase
    {
        public const string ControlKeyStyle = "ControlKey";
        #region fields

        public static readonly Command KeyCommand = new Command { Routing = RoutingStrategy.Direct };

        public const int KeyboardHeight = 80 * 4;

        private int nextKeyIndex;
        private KeyboardLayouts keyboards;
        //private ImageShape snapshotImage;
        //private Texture snapshot;
        private KeyToolTip toolTip;
        private KeyButton shiftKey;
        private KeyButton backSpaceKey;
        private KeyButton returnKey;
        private KeyButton ctrlKey;
        private KeyButton pressedButton;

        private bool isShiftLocked;
        private KeyboardState state = KeyboardState.None;
        private KeyboardLayout keyboard;

        private List<KeyButton> buttons;

        #endregion
        #region ctor/dtor

        public Keyboard()
            : base()
        {
            buttons = new List<KeyButton>();

            ButtonHeight = 80;
            ButtonWidth = 48;

            toolTip = new KeyToolTip();

            ControlFlags &= ~ControlFlags.CanFocus;
            ControlFlags |= ControlFlags.HitTestVisible | ControlFlags.EnableLongPress | ControlFlags.CanPan;
            InitializeKeyboards();
            Size = new Size(480, KeyboardHeight);
            OnStateChanged();
        }

        protected override void Dispose(bool dispose)
        {
            KeyCommand.Executed -= OnKeyPress;
            if (toolTip != null) toolTip.Dispose();
            base.Dispose(dispose);
        }

        #endregion
        #region props

        public KeyButton PressedButton
        {
            get { return pressedButton; }
            set
            {
                if (pressedButton != value)
                {
                    KeyButton old = pressedButton;
                    pressedButton = value;
                    OnPressedButtonChanged(old);
                }
            }
        }
        public int ButtonHeight { get; private set; }
        public int ButtonWidth { get; private set; }
        public int ButtonSpace { get { return 6; } }

        public bool IsShiftLocked
        {
            get { return isShiftLocked; }
            set
            {
                if (isShiftLocked != value)
                {
                    isShiftLocked = value;
                    OnShiftLockedChanged();
                }
            }
        }

        public KeyboardState State
        {
            get { return state; }
            set
            {
                if (state != value)
                {
                    IsShiftLocked = false;
                    state = value;
                    OnStateChanged();
                }
            }
        }

        public override bool CanFocus
        {
            get { return false; }
        }

        #endregion
        #region methods

        private void OnShiftLockedChanged()
        {
            shiftKey.IsPressed = isShiftLocked;
        }

        private void OnPressedButtonChanged(KeyButton old)
        {
            if (old != null) old.IsPressed = false;
            if (pressedButton != null) pressedButton.IsPressed = true;
            SetToolTip(pressedButton);
        }

        private void InitializeKeyboards()
        {
            int space = ButtonSpace;
            int medWidth = (ButtonWidth) * 3 / 2 - space;
            int bigWidth = (ButtonWidth * 2) - space;
            int h = ButtonHeight - space;
            shiftKey = new KeyButton { Size = new Size(medWidth, h), Color = PhoneColors.MediumDark2, StyleName = ControlKeyStyle, IsControl = true };
            backSpaceKey = new KeyButton { Size = new Size(medWidth, h), Keys = "\b", Color = PhoneColors.MediumDark2, StyleName = ControlKeyStyle, IsControl = true };
            returnKey = new KeyButton { Size = new Size(bigWidth, h), Keys = "\n", Color = PhoneColors.MediumDark2, StyleName = ControlKeyStyle, IsControl = true };
            ctrlKey = new KeyButton { Size = new Size(bigWidth, h), Color = PhoneColors.MediumDark2, StyleName = ControlKeyStyle, IsControl = true };

            shiftKey.Tap += new EventHandler<TapEventArgs>(shiftKey_Tap);
            shiftKey.DoubleTap += new EventHandler<TapEventArgs>(shiftKey_DoubleTap);
            ctrlKey.Tap += new EventHandler<TapEventArgs>(ctrlKey_Tap);

            keyboards.Default = new KeyboardLayout { Row1 = "qwertyuiop", Row2 = "asdfghjkl", Row3 = "zxcvbnm", Ctrl = "&123" };
            keyboards.Shift = new KeyboardLayout { Row1 = "QWERTYUIOP", Row2 = "ASDFGHJKL", Row3 = "ZXCVBNM", Ctrl = "&123" };
            keyboards.Control = new KeyboardLayout { Row1 = "1234567890", Row2 = @"@#$%&()-•\", Row3 = "!;:'\"?/", Ctrl = "abcd", Shift = SysImages.Forward };
            keyboards.ShiftControl = new KeyboardLayout { Row1 = "1234567890", Row2 = "^[]{}<>€£§", Row3 = "*-+=_~|", Ctrl = "abcd", Shift = SysImages.Backward };
            KeyCommand.Executed += new EventHandler<CommandEventArgs>(OnKeyPress);
        }

        void shiftKey_DoubleTap(object sender, TapEventArgs e)
        {
            if (State == KeyboardState.None)
            {
                State = KeyboardState.Shift;
                IsShiftLocked = true;
            }
        }

        void ctrlKey_Tap(object sender, TapEventArgs e)
        {
            State = (state == KeyboardState.None || state == KeyboardState.Shift) ? KeyboardState.Control : KeyboardState.None;
        }

        void shiftKey_Tap(object sender, TapEventArgs e)
        {
            switch (state)
            {
                case KeyboardState.None: State = KeyboardState.Shift; break;
                case KeyboardState.Control: State = KeyboardState.ShiftControl; break;
                case KeyboardState.Shift: State = KeyboardState.None; break;
                case KeyboardState.ShiftControl: State = KeyboardState.Control; break;
            }
        }

        protected void ResetKeyPool()
        {
            nextKeyIndex = 0;
        }

        internal KeyButton GetButton()
        {
            KeyButton btn;
            if (buttons.Count > nextKeyIndex)
            {
                btn = buttons[nextKeyIndex];
            }
            else
            {
                btn = new KeyButton();
                buttons.Add(btn);
            }
            nextKeyIndex++;
            btn.Size = new Size(ButtonWidth - ButtonSpace, ButtonHeight - ButtonSpace);
            btn.IsSpace = false;
            VisibleControls.Add(btn);
            return btn;
        }

        protected void OnStateChanged()
        {
            switch (state)
            {
                case KeyboardState.None: keyboard = keyboards.Default; break;
                case KeyboardState.Shift: keyboard = keyboards.Shift; break;
                case KeyboardState.Control: keyboard = keyboards.Control; break;
                case KeyboardState.ShiftControl: keyboard = keyboards.ShiftControl; break;
                default: throw new NotSupportedException();
            }
            Invalidate(InvalidationFlag.Internal);
        }

        protected override void OnLayout()
        {
            if (CheckFlag(InvalidationFlag.Internal | InvalidationFlag.Size))
            {
                LayoutKeyboard(keyboard);
            }
            base.OnLayout();
        }

        protected override void OnSizeChanged()
        {
            ButtonWidth = Width / 10;
            ButtonHeight = Screen.Orientation == Orientation.Portrait ? 80 : 64;
            Height = 4 * ButtonHeight;
            SetButtonSize();
            base.OnSizeChanged();
        }

        private void SetButtonSize()
        {
            int space = ButtonSpace;
            int medWidth = (ButtonWidth) * 3 / 2 - space;
            int bigWidth = (ButtonWidth * 2) - space;
            int h = ButtonHeight - space;
            shiftKey.Size = new Size(medWidth, h);
            backSpaceKey.Size = new Size(medWidth, h);
            returnKey.Size = new Size(bigWidth, h);
            ctrlKey.Size = new Size(bigWidth, h);
        }

        private void LayoutKeyboard(KeyboardLayout keyboard)
        {
            VisibleControls.Clear();
            VisibleControls.Add(shiftKey, backSpaceKey, returnKey, ctrlKey);
            ResetKeyPool();
            keyboard.LayoutRows(this, shiftKey, backSpaceKey, ctrlKey, returnKey);
        }

        protected override Shape CreateDefaultBackground()
        {
            return new RectangleShape { Color = Color.Black };
        }

        private void SetToolTip(KeyButton button)
        {
            if (button != null && !button.IsControl && !button.IsSpace)
            {
                Window w = Window;
                if (w != null)
                {
                    Screen.Current.Attach(toolTip);
                    Point p = button.Location;
                    Rectangle rect = w.KeyboardRect;
                    if (Parent != null) p.Offset(rect.X, rect.Y);
                    p.Offset(w.X, w.Y);
                    p.Y -= toolTip.Height;
                    p.X -= (toolTip.Width - button.Width) / 2;
                    if (p.X < 0) p.X = 0;
                    else if (p.X > (Screen.Width - toolTip.Width)) p.X = Screen.Width - toolTip.Width;
                    toolTip.Location = p;
                    string txt = button.Keys;
                    toolTip.Text = txt;
                }
            }
            else
            {
                Screen.Current.Detach(toolTip);
            }
        }

        protected override void OnVisibleChanged()
        {
            base.OnVisibleChanged();
            if (!Visible)
            {
                IsShiftLocked = false;
                State = KeyboardState.None;
            }
        }

        private void OnKeyPress(object sender, CommandEventArgs e)
        {
            KeyButton btn = e.Owner as KeyButton;
            if (!string.IsNullOrEmpty(btn.Keys))
            {
                SendKeys(btn.Keys);
            }
            e.Handled = true;
        }

        private void SendKeys(string keys)
        {
            foreach (char c in keys)
            {
                Screen.Current.MessageServer.KeyPress(c);
            }
            if (State == KeyboardState.Shift && !IsShiftLocked) State = KeyboardState.None;
        }

        protected internal override void OnTouchDown(TouchEventArgs e)
        {
            // don't allow any ancestor to set the focus:
            e.FocusSet = true;
            PressedButton = GetButtonFromPoint(e.Position);
            base.OnTouchDown(e);
        }

        private Button lastButton;

        protected internal override void OnTouchUp(TouchEventArgs e)
        {
            if (pressedButton != null) pressedButton.OnTap(new TapEventArgs());
            lastButton = pressedButton;
            PressedButton = null;
            e.Handled = true;
            e.AllowDoubleTap = true;
            base.OnTouchUp(e);
        }

        protected internal override void OnDoubleTap(TapEventArgs e)
        {
            if (lastButton == shiftKey)
            {
                shiftKey.OnDoubleTap(e);
            }
            base.OnDoubleTap(e);
        }

        protected internal override void OnBeginPan(PanEventArgs e)
        {
            e.Handled = true;
        }

        protected internal override void OnPan(PanEventArgs e)
        {
            PressedButton = GetButtonFromPoint(e.Position);
        }

        protected internal override void OnHold(TouchEventArgs e)
        {
            e.Handled = true;
            e.Cancel = true;
        }

        private KeyButton GetButtonFromPoint(Point point)
        {
            point = PointToClient(point);
            Rectangle rect = Window.KeyboardRect;
            rect.Location = Point.Empty;
            if (pressedButton != null && pressedButton.Bounds.Contains(point)) return pressedButton;

            foreach (KeyButton btn in VisibleControls)
            {
                rect = btn.Bounds;
                rect.Inflate(ButtonSpace, ButtonSpace);
                if (rect.Contains(point)) return btn;
            }
            return null;
        }


        #endregion
    }

    #region InternalToolTip
    class KeyToolTip : Control
    {
        private BubbleShape background;
        private StringShape text;
        public KeyToolTip()
        {
            background = new BubbleShape { ArrowOffset = 0, Color = PhoneColors.White };
            text = new StringShape { Color = PhoneColors.Black, Alignment = Alignment.Center, LineAlignment = Alignment.Center, Font = FontSprite.FromData(GlFontFamily.GenericSansSerif, 24, FontStyle.Bold, false) };
            //background = new BackgroundShape { Texture = SysImages.Accent, Color = ColorUtil.FromArgb(240, 255, 255, 255), Style = RenderStyle.Edged };
            //background = new BubbleShape { Color = ColorUtil.FromARGB(240, 255, 255, 255) };
            Shapes.AddRange(background, text);
            Size = new Size(100, 100 + background.ArrowSize);
        }

        public string Text
        {
            get { return text.Text; }
            set
            {
                text.Text = value;
                Invalidate(InvalidationFlag.Text);
            }
        }


        protected override void OnLayout()
        {
            if (CheckFlag(InvalidationFlag.Size | InvalidationFlag.Layout))
            {
                background.Size = Size;
                text.Size = new Size(Width, Height - background.ArrowSize);
            }
            base.OnLayout();
        }


    }
    #endregion
}
